// 中断处理相关代码
#include "common.h"
#include "riscv.h"
#include "memlayout.h"
#include "spinlock.h"
#include "proc.h"

extern char trampoline[], uservec[], userret[];

void kernelvec();        // 在kernelvec中定义，S模式中断处理入口，通过声明，可以在当前C代码中使用

// 初始化内核中断处理程序
void trapinithart(void) {
    // 将S模式中断处理入口地址写入STVEC寄存器，发生中断时会跳转到该地址
    w_csr(STVEC, (uint64)kernelvec);
    printf("hart%d：内核程序陷入地址初始化完成!\n", r_tp());
}

// 来自内核的中断处理程序
void kerneltrap(void) {
    // 保存重要CSR寄存器
    uint64 sstatus = r_csr(SSTATUS);     // S模式状态寄存器
    uint64 scause = r_csr(SCAUSE);       // 中断原因
    uint64 sepc = r_csr(SEPC);           // 中断指令地址，用于中断返回
    printf("csr寄存器保存完毕!\n");
    // 检查中断前的模式，如果是M模式，则不应该在这里处理
    if((sstatus & SSTATUS_SPP) == 0) {
        panic("S模式中断处理程序被M模式调用!");
    }
    // 处理中断时中断应该被关闭，即在此期间不接受新的中断 
    else if((sstatus & SSTATUS_SIE) != 0) {
        panic("中断没有关闭!");
    }
    // 处理外部中断
    else if(scause == INTR_EXTERNAL) {
        printf("外部中断!\n");
    }
    // 处理时钟中断
    else if(scause == INTR_TIMER) {
        printf("时钟中断!\n");
    }
    // 处理未知中断
    else {
        panic("未知中断!");
    }

    // 恢复重要寄存器，保证中断能回到原来的状态
    w_csr(SEPC, sepc);
    w_csr(SSTATUS, sstatus);
}

// 用户程序陷入处理程序（来自用户空间的中断、异常和系统调用）
void usertrap(void) {
    if((r_csr(SSTATUS) & SSTATUS_SPP) != 0) {
        panic("usertrap: 不是来自用户空间的陷入！");
    }
    // 当前处于内核态，发生错误时将由kernelvec处理
    w_csr(STVEC, (uint64)kernelvec);

    // 保存陷入地址，防止内核态陷入改变寄存器
    struct proc *p = getproc();
    p->trapframe->epc = r_csr(SEPC);
    // 处理系统调用
    if(r_csr(SCAUSE) == 8) {
        // 如果进程已被杀死，直接退出
        if(killed(p)) {
            exit(-1);
        }
        // 指向发起系统调用的下一条指令，避免重复陷入
        p->trapframe->epc += 4;
        // 在完成寄存器保存之后开启中断
        intr_on();
        // 系统调用分发程序
        syscall();
    } else {
        printf("usertrap: 非系统调用！ scause = 0x%x, pid = %d\n", r_csr(SCAUSE), p->pid);
        printf("sepc = 0x%x, stval = 0x%x\n", r_csr(SEPC), r_csr(STVAL));
    }
    usertrapret();
}
// 从内核态返回用户态
void usertrapret(void) {
    // 获取当前在cpu上执行的进程，设置下一次陷入时所需的必要寄存器
    struct proc *p = getproc();
    // 关闭中断，避免返回被打断，丢失数据
    intr_off();

    // 设置stvec寄存器为uservec，因为返回之后处于用户态，下一次陷入时应该执行的是用户态的陷入程序
    uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
    w_csr(STVEC, trampoline_uservec);
    // 记录下次陷入时会用到的值
    p->trapframe->kernel_satp = r_csr(SATP);
    p->trapframe->kernel_sp = p->kstack + PGSIZE;
    p->trapframe->kernel_trap = (uint64)usertrap;
    p->trapframe->kernel_hartid = r_tp();
    
    // 设置sret所用到的寄存器
    // 设置SPP位为0，即切换到用户态
    uint64 x = r_csr(SSTATUS);
    x &= ~SSTATUS_SPP;           // 清空SPP位，即为0（用户模式）
    x |= SSTATUS_SIE;           // 允许中断
    w_csr(SSTATUS, x);
    // 设置sepc寄存器为保存的进程pc，即返回陷入处继续执行
    w_csr(SEPC, p->trapframe->epc);
    // 设置satp寄存器为进程页表，返回后切换到进程地址空间
    uint64 satp = SATP_MODE_SV39 | (((uint64)p->pagetable) >> 12);
    // 调用trampoline.S中的userret函数，恢复相关寄存器，返回用户态
    // 将satp值作为参数传递，RISCV约定，函数调用的第一个参数保存在a0寄存器中
    // 因此调用之后，使用a0寄存器中的值写入satp寄存器
    // 调用之后再切换到进程页表，保证内核态代码可以在内核页表下正确跳转和执行
    uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
    ((void (*)(uint64))trampoline_userret)(satp);
}